package logic.schedulers;

import java.io.File;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Vector;
import java.util.regex.Pattern;
import javax.swing.JFrame;
import logicTests.Dbg;

/**
 * Trieda zodpovedna za dynamicke natahovanie pluginov. V hlavnom vlakne
 * sa musi inicializovat metodou loadAlgorithms(). V podprocese jedneho
 * simulacneho behu ju nie je potrebne inicializovat, staci len pouzit
 * metodu loadAlgorithm(meno algoritmu).
 */
public class AlgorithmCollection {
    private static Vector<AbstractSchedulerOptimization> localScheduling = 
                new Vector<AbstractSchedulerOptimization>();
    private static Vector<AbstractGlobalScheduling> globalScheduling = 
                new Vector<AbstractGlobalScheduling>();

    private static File pluginDir = new File("plugins");
    private static ClassLoader classLoader = null;
    
    /**
     * Inicializacia class loader-a.
     */
    private static void initLoader() {
        if (classLoader != null)
            return;
        
        classLoader = new URLClassLoader(
                new URL[] {getPluginDirURL()},
                AlgorithmCollection.class.getClassLoader()
                );
        
    }
    
    /**
     * Transformacia cesty ku adresaru s pluginmi na URL
     */
    private static URL getPluginDirURL() {
        System.out.println("PluginDir " + pluginDir.getAbsolutePath());
        URL pluginURL = null;
        try {
            pluginURL = pluginDir.getAbsoluteFile().toURI().toURL();
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        }
        return pluginURL;
    }
    
    /**
     * Dynamicke natiahnutie novej triedy.
     * @param   className   plne kvalifikovane meno triedy
     * @return              nova instancia danej triedy
     */
    private static AlgorithmPlugin loadClass(String className) {
        AlgorithmPlugin result = null;
        try {
            Class trieda = classLoader.loadClass(className);
            int modifiers = trieda.getModifiers();
            if (!Modifier.isAbstract(modifiers) &&
                !Modifier.isInterface(modifiers) &&
                AlgorithmPlugin.class.isAssignableFrom(trieda)) {

                result = (AlgorithmPlugin) (trieda.newInstance());
            }
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        }
        
        return result;
    }
    
    /**
     * Nacitanie vsetkych potomkov rozhrania AlgorithmPlugin z adresara plugin-ov.
     */
    public static void loadAlgorithms() {
        initLoader();
        localScheduling.add(new NoOptimization());
        globalScheduling.add(new FirstSuitableSched());

        LinkedList<File> newFiles = new LinkedList<File>();
        File[] files = pluginDir.listFiles();
        if (files == null)
            return;
        
        for (int i = 0; i < files.length; i++) {
            newFiles.add(files[i]);
        }
        
        while (!newFiles.isEmpty()) {
            File nextFile = newFiles.removeLast();
            if (nextFile.isDirectory() && !nextFile.isHidden()) {
                Dbg.vypis("Adding directory " + nextFile.getName());
                files = nextFile.listFiles();
                if (files != null) {
                    for (int i = 0; i < files.length; i++) {
                        newFiles.push(files[i]);
                    }
                }
                continue;
            }
            String fileName = nextFile.getAbsolutePath().substring(pluginDir.getAbsolutePath().length()+1);
            if (fileName.endsWith(".class")) {
                String triedaStr = fileName;
                triedaStr = triedaStr.substring(0,triedaStr.lastIndexOf("."));
                triedaStr = triedaStr.replaceAll(Pattern.quote(File.separator),".");

                AlgorithmPlugin ap = loadClass(triedaStr);

                if (ap instanceof AbstractGlobalScheduling)
                    globalScheduling.add((AbstractGlobalScheduling)ap);
                else if (ap instanceof AbstractSchedulerOptimization)
                    localScheduling.add((AbstractSchedulerOptimization)ap);
            }
        }
        Collections.sort(localScheduling, new AlgComparator());
        Collections.sort(globalScheduling, new AlgComparator());
    }

    /**
     * Pre vsetky plugin-y zavola metodu createParamDialog.
     */
    public static void createDialogs(JFrame owner) {
        for (AlgorithmPlugin elem : globalScheduling) {
            elem.createParamDialog(owner);
        }
        for (AlgorithmPlugin elem : localScheduling) {
            elem.createParamDialog(owner);
        }
    }
    
    /**
     * Nacitanie konkretneho algoritmu planovania z adresara plugin-ov.
     */
    public static AlgorithmPlugin loadAlgorithm(String className) {
        initLoader();
        return loadClass(className);
    }
    
    private static class AlgComparator implements Comparator<AlgorithmPlugin> {
        public int compare(AlgorithmPlugin o1, AlgorithmPlugin o2) {
            return o1.getIndex() - o2.getIndex();
        }
        
    }
    
    /**
     * @return  pocet lokalnych planovacov
     */
    public static int getLocalSchedulingCount() {
        return localScheduling.size();
    }
    
    /**
     * @return  pocet globalnych planovacov
     */
    public static int getGlobalSchedulingCount() {
        return globalScheduling.size();
    }

    public static AbstractSchedulerOptimization getLocalScheduling(int index) {
        return localScheduling.elementAt(index);
    }
    
    public static AbstractGlobalScheduling getGlobalScheduling(int index) {
        return globalScheduling.elementAt(index);
    }
}
